﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LockCrypt.Core {
    /// <summary>
    /// Comparer for two sorted collections which applies different actions when an item exists in one collection, the other collection, or both.
    /// </summary>
    public class SortedCollectionComparer {
        /// <summary>
        /// Compares two sorted collections, applying different actions when an item exists in one collection, the other collection, or both.
        /// </summary>
        /// <typeparam name="T">The type of items in the collections</typeparam>
        /// <param name="source">The first collection.</param>
        /// <param name="destination">The second collection.</param>
        /// <param name="comparer">The comparer to determine if two items are equal.</param>
        /// <param name="onLeftOnly">Action to perform when an item only exists in the first collection.</param>
        /// <param name="onRightOnly">Action to perform when an item only exists in the second collection.</param>
        /// <param name="onBoth">Action to perform when an item exists in both collections.</param>
        public static void CompareSortedCollections<T>(IEnumerable<T> source, IEnumerable<T> destination, IComparer<T> comparer, Action<T> onLeftOnly, Action<T> onRightOnly, Action<T, T> onBoth) {
            EnumerableIterator<T> sourceIterator = new EnumerableIterator<T>(source);
            EnumerableIterator<T> destinationIterator = new EnumerableIterator<T>(destination);

            while(sourceIterator.HasCurrent && destinationIterator.HasCurrent) {
                // While LHS < RHS, the items in LHS aren't in RHS
                while(sourceIterator.HasCurrent && (comparer.Compare(sourceIterator.Current, destinationIterator.Current) < 0)) {
                    onLeftOnly(sourceIterator.Current);
                    sourceIterator.MoveNext();
                }

                // While RHS < LHS, the items in RHS aren't in LHS
                while(sourceIterator.HasCurrent && destinationIterator.HasCurrent && (comparer.Compare(sourceIterator.Current, destinationIterator.Current) > 0)) {
                    onRightOnly(destinationIterator.Current);
                    destinationIterator.MoveNext();
                }

                // While LHS==RHS, the items are in both
                while(sourceIterator.HasCurrent && destinationIterator.HasCurrent && (comparer.Compare(sourceIterator.Current, destinationIterator.Current) == 0)) {
                    onBoth(sourceIterator.Current, destinationIterator.Current);
                    sourceIterator.MoveNext();
                    destinationIterator.MoveNext();
                }
            }

            // Mop up.
            while(sourceIterator.HasCurrent) {
                onLeftOnly(sourceIterator.Current);
                sourceIterator.MoveNext();
            }

            while(destinationIterator.HasCurrent) {
                onRightOnly(destinationIterator.Current);
                destinationIterator.MoveNext();
            }
        }
    }

    internal class EnumerableIterator<T> {
        private readonly IEnumerator<T> enumerator;

        public EnumerableIterator(IEnumerable<T> enumerable) {
            enumerator = enumerable.GetEnumerator();
            MoveNext();
        }

        public bool HasCurrent { get; private set; }

        public T Current {
            get { return enumerator.Current; }
        }

        public void MoveNext() {
            HasCurrent = enumerator.MoveNext();
        }
    }
}